package com.cy.interceptor;

import com.jfinal.aop.Interceptor;
import com.jfinal.core.ActionInvocation;
import com.jfinal.core.Controller;
import com.jfinal.plugin.ehcache.CacheKit;
import com.jfinal.plugin.ehcache.CacheName;
import com.jfinal.render.Render;
import com.cy.ext.plugin.cache.NoCache;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 13-11-22
 * Time: 下午6:50
 * 只缓存JSON数据，其他并不缓存。注意使用，如果无需缓存可使用
 * NoCache.class
 */
public class JSONCacheInterceptor implements Interceptor {
    private static final String renderKey = "$jsonRenderKey$";
    private static volatile ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<String, ReentrantLock>();

    private ReentrantLock getLock(String key) {
        ReentrantLock lock = lockMap.get(key);
        if (lock != null)
            return lock;

        lock = new ReentrantLock();
        ReentrantLock previousLock = lockMap.putIfAbsent(key, lock);
        return previousLock == null ? lock : previousLock;
    }

    final public void intercept(ActionInvocation ai) {
        Controller controller = ai.getController();
        if (isNoCache(ai, controller)) {
            ai.invoke();
        } else {
            String cacheName = buildCacheName(ai, controller);
            String cacheKey = buildCacheKey(ai, controller);
            Map<String, Object> cacheData = CacheKit.get(cacheName, cacheKey);
            if (cacheData == null) {
                Lock lock = getLock(cacheName);
                lock.lock();                    // prevent cache snowslide
                try {
                    cacheData = CacheKit.get(cacheName, cacheKey);
                    if (cacheData == null) {
                        ai.invoke();
                        cacheAction(cacheName, cacheKey, controller);
                        return;
                    }
                } finally {
                    lock.unlock();
                }
            }

            useCacheDataAndRender(cacheData, controller);
        }

    }

    private boolean isNoCache(ActionInvocation ai, Controller controller) {
        NoCache noCache = ai.getMethod().getAnnotation(NoCache.class);
        if (noCache != null)
            return noCache.value();
        noCache = controller.getClass().getAnnotation(NoCache.class);
        return (noCache != null) ? noCache.value() : false;
    }

    // TODO 考虑与 EvictInterceptor 一样强制使用  @CacheName
    private String buildCacheName(ActionInvocation ai, Controller controller) {
        CacheName cacheName = ai.getMethod().getAnnotation(CacheName.class);
        if (cacheName != null)
            return cacheName.value();
        cacheName = controller.getClass().getAnnotation(CacheName.class);
        return (cacheName != null) ? cacheName.value() : ai.getActionKey();
    }

    private String buildCacheKey(ActionInvocation ai, Controller controller) {
        StringBuilder sb = new StringBuilder(ai.getActionKey());
        String urlPara = controller.getPara();
        if (urlPara != null)
            sb.append("/").append(urlPara);

        String queryString = controller.getRequest().getQueryString();
        if (queryString != null)
            sb.append("?").append(queryString);
        return sb.toString();
    }

    private void cacheAction(String cacheName, String cacheKey, Controller controller) {
        Map<String, Object> cacheData = new HashMap<String, Object>();
        cacheData.put(renderKey, controller.getRender());        // cache render
        CacheKit.put(cacheName, cacheKey, cacheData);
    }

    private void useCacheDataAndRender(Map<String, Object> cacheData, Controller controller) {
        HttpServletRequest request = controller.getRequest();
        Set<Map.Entry<String, Object>> set = cacheData.entrySet();
        for (Iterator<Map.Entry<String, Object>> it = set.iterator(); it.hasNext(); ) {
            Map.Entry<String, Object> entry = it.next();
            request.setAttribute(entry.getKey(), entry.getValue());
        }
        request.removeAttribute(renderKey);

        controller.render((Render) cacheData.get(renderKey));        // set render from cacheData
    }
}
